package error;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import node.ExpNode;
import node.FuncRParamsNode;
import symbol.ArraySymbol;
import symbol.BasicSymbol;
import symbol.FuncSymbol;
import symbol.ScopeStack;
import symbol.Symbol;
import symbol.SymbolKind;

public class Error {
    private static Error instance; // 单例模式
    // 为了解决左值回退的重复插入问题
    public final Set<ErrorContext> errorSet = new HashSet<>();// 错误处理结果
    public BufferedWriter errorWriter;
    public boolean hasError = false; // 是否有错误
    public int inFor = 0; // 是否在for循环中,当为0时不在for循环中，否则在for循环中
    public boolean inIntFunc = false; // 是否在int函数中
    public boolean inVoidFunc = false; // 是否在void函数中
    public boolean inCharFunc = false; // 是否在char函数中
    private ScopeStack scopeStack = ScopeStack.getInstance();

    private Error() {
    }

    public static Error getInstance() {
        if (instance == null) {
            instance = new Error();
        }
        return instance;
    }

    public void initBuffer(String errorFile) throws IOException {
        errorWriter = new BufferedWriter(new FileWriter(errorFile));
    }

    public void closeBuffer() throws IOException {
        errorWriter.close();
    }

    public void addError(int lineno, ErrorType errorType) {
        hasError = true;
        errorSet.add(new ErrorContext(lineno, errorType));
    }

    public boolean inFor() {
        return inFor != 0;
    }

    // H: 不能对常量赋值
    public void checkConstantAssignment(Symbol symbol, int lineno) {
        if (symbol instanceof ArraySymbol arraySymbol && arraySymbol.getKind() == SymbolKind.CONSTANT ||
                symbol instanceof BasicSymbol basicSymbol && basicSymbol.getKind() == SymbolKind.CONSTANT) {
            addError(lineno, ErrorType.H);
        }
    }

    // L:printf函数参数类型不匹配
    public void checkPrintfType(String stringConst, List<ExpNode> expNodeList, int lineno) {
        int count_cd = 0;
        for (int i = 0; i < stringConst.length(); i++) {
            if (stringConst.charAt(i) == '%' && i + 1 < stringConst.length()) {
                if (stringConst.charAt(i + 1) == 'c') {
                    count_cd++;
                } else if (stringConst.charAt(i + 1) == 'd') {
                    count_cd++;
                }
            }
        }
        int count_exp = expNodeList.size();
        if (count_cd != count_exp) {
            addError(lineno, ErrorType.L);
        }
    }

    // D:函数参数个数不匹配
    public void checkFuncParams(String identNode, int lineno, FuncRParamsNode funcRParamsNode) {
        int paramNum = funcRParamsNode.getExpNodes().size();
        FuncSymbol symbol = (FuncSymbol) scopeStack.lookup(identNode);

        // 如果函数存在则检查参数个数与类型
        if (symbol != null) {
            if (paramNum != symbol.getParamNum()) {
                addError(lineno, ErrorType.D);
            } else {
                checkParamTypes(funcRParamsNode, symbol.getParamList(), lineno);
            }
        }
    }

    private void checkParamTypes(FuncRParamsNode funcRParamsNode, List<Symbol> paramList, int lineno) {
        for (int i = 0; i < paramList.size(); i++) {
            Symbol param = paramList.get(i);
            ExpNode exp = funcRParamsNode.getExpNodes().get(i);
            Symbol realParam = exp.getIdent() != null ? scopeStack.lookup(exp.getIdent()) : null;

            boolean isArray = realParam instanceof ArraySymbol;
            if (param instanceof ArraySymbol) {
                if (isArray) {
                    // 两者都是数组
                    ArraySymbol realArray = (ArraySymbol) param;
                    ArraySymbol expArray = (ArraySymbol) realParam;
                    if (realArray.getType() != expArray.getType()) {
                        addError(lineno, ErrorType.E);
                    }
                } else
                    // 一个是数组，一个不是
                    addError(lineno, ErrorType.E);
            } else if (isArray)
                // 一个是基本类型，一个是数组
                addError(lineno, ErrorType.E);
        }
    }

    public void showError() throws IOException {
        List<ErrorContext> errorList = new ArrayList<>(errorSet);
        errorList.sort((o1, o2) -> o1.getLineno() - o2.getLineno());
        for (ErrorContext error : errorList) {
            errorWriter.write(error.getLineno() + " " + error.getErrorType().toString() + "\n");
        }
    }

}
